﻿/*
 *  RacerLib - XNA Racing Game Level and Tile Management Library
 * 
 *  Copyright (C) 2009 Sebastian Schäfer
 *  
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; version 2 of the License.
 *  
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  
 *  You should have received a copy of the GNU General Public License 
 *  along with this program; if not, see <http://www.gnu.org/licenses/>.
 */
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

using gdv.VCPR.Racer;

namespace RacerEditorWIN
{
    public partial class TilePanel : Panel
    {
        #region private variables
        private Collection<Tile> tiles;
        private TileSet tileSet;

        private Color gridColor;
        private Pen gridPen;
        private bool gridShow;
        private Bitmap gridBmp;

        private Color selectionColor;
        private Pen selectionPen;

        private Bitmap bmp;
        private Bitmap bmpSelection;

        private bool mouseIn;

        private VScrollBar vScroll;
        private HScrollBar hScroll;
        #endregion

        public int TileWidth { get; protected set; }
        public int TileHeight { get; protected set; }

        public int TilesCountX { get; protected set; }
        public int TilesCountY { get; protected set; }

        public TileSet Tileset
        {
            set
            {
                tileSet = value;
                if (IsTilePicker)
                    GenerateTiles();
                SetTileSize(TileWidth, TileHeight);
            }
        }

        public Collection<Tile> Tiles
        {
            get
            {
                return tiles;
            }

            set 
            { 
                tiles = value; 
            }
        }

        /// <summary>
        /// If set the grid is drawn
        /// </summary>
        public bool ShowGrid { get { return gridShow; } set { gridShow = value; Refresh(); } }
        /// <summary>
        /// The Color of the Grid
        /// </summary>
        public Color GridColor { get { return gridColor; } set { SetGridColor(value); } }

        /// <summary>
        /// Show the Tile under the mouse
        /// </summary>
        public bool ShowTileMouse { get; set; }
        /// <summary>
        /// The tile's position is the gridcoordinate from the mouseposition
        /// </summary>
        public Tile TileMouse { get; set; }

        /// <summary>
        /// Show the selected Tile
        /// </summary>
        public bool ShowTileSelected { get; set; }
        /// <summary>
        /// The "selected" Tile
        /// </summary>
        public Tile TileSelected { get; set; }
        /// <summary>
        /// The image that is used for selection
        /// </summary>
        public Bitmap TileSelection { get; set; }
        /// <summary>
        /// The Color of the Grid
        /// </summary>
        public Color TileSelectionColor { get { return selectionColor; } set { SetSelectionColor(value); } }


        public Tile TileSelectionStart { get; set; }
        public Tile TileSelectionEnd { get; set; }
        public Rectangle Selection 
        {
            get 
            {
                int xMin = Math.Min(TileSelectionStart.X, TileSelectionEnd.X);
                int xMax = Math.Max(TileSelectionStart.X, TileSelectionEnd.X);

                int yMin = Math.Min(TileSelectionStart.Y, TileSelectionEnd.Y);
                int yMax = Math.Max(TileSelectionStart.Y, TileSelectionEnd.Y);

                return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin);
            }
        }

        public Collection<Tile> TilesSelected 
        { 
            get 
            {
                Collection<Tile> tiles = new Collection<Tile>();
                
                foreach (Tile t in Tiles)
                    if (InsideSelection(t))
                        tiles.Add(t);

                return tiles;
            } 
        }

        public bool SelectionMoveMode { get; set; }
        public bool SelectionPasteMode { get; set; }

        public Point SelectionMoveOffset { get; set; }


        /// <summary>
        /// if set rearrange the tiles automatically to show all present
        /// </summary>
        public bool IsTilePicker { get; set; }

        /// <summary>
        /// Create a new instance
        /// </summary>
        public TilePanel()
        {
            InitializeComponent();

            Init();
        }

        /// <summary>
        /// basic inits, so all varaibles are created
        /// </summary>
        private void Init()
        {
            //basic settings
            this.DoubleBuffered = true;

            //variable creation
            tiles = new Collection<Tile>();
            InitScrollBars();

            //grid settings
            GridColor = Color.White;
            ShowGrid = true;
            SetTileSize(Width, Height);
            CreateGrid();

            //tile settings
            ShowTileMouse = false;
            TileMouse = new Tile();

            //selected Tile Settings
            ShowTileSelected = false;
            TileSelected = new Tile();
            TileSelectionColor = Color.Orange;
            TileSelectionStart = TileSelected;
            TileSelectionEnd = TileSelected;

            CreateBitmaps(Width, Height);

            //MouseWheel
            IsTilePicker = false;
            this.MouseWheel += new MouseEventHandler(TilePanel_MouseWheel);
            mouseIn = false;

            SelectionMoveMode = false;
            SelectionMoveOffset = new Point();

        }

        void InitScrollBars()
        {
            vScroll = new VScrollBar();
            vScroll.Dock = DockStyle.Right;
            vScroll.Visible = false;
            vScroll.Minimum = 0;
            vScroll.Scroll += new ScrollEventHandler(Scroll_Refresh);
            vScroll.LargeChange = 1;
            vScroll.SmallChange = 1;
            this.Controls.Add(vScroll);

            hScroll = new HScrollBar();
            hScroll.Dock = DockStyle.Bottom;
            hScroll.Visible = false;
            hScroll.Minimum = 0;
            hScroll.Scroll += new ScrollEventHandler(Scroll_Refresh);
            hScroll.LargeChange = 1;
            hScroll.SmallChange = 1;
            this.Controls.Add(hScroll);
        }

        void Scroll_Refresh(object sender, ScrollEventArgs e)
        {
            Refresh();    
        }

        void TilePanel_MouseWheel(object sender, MouseEventArgs e)
        {
            if (mouseIn)
            {
                if (sender != null)
                    if (sender.GetType() == typeof(TilePanel))
                        if (Math.Sign(e.Delta) > 0)
                            ZoomIn();
                        else
                            ZoomOut();
            }
        }

        public void ZoomIn()
        {
            SetTileSize(Math.Min(this.Width, Math.Max(3, TileWidth + 1)));
        }

        public void ZoomOut()
        {
            SetTileSize(Math.Min(this.Width, Math.Max(3, TileWidth - 1)));
        }

        /// <summary>
        /// Set a gridcolor
        /// </summary>
        /// <param name="color">[...]</param>
        private void SetGridColor(Color color)
        {
            gridColor = color;
            gridPen = new Pen(color);

            CreateGrid();
            Invalidate();
        }

        /// <summary>
        /// Set a gridcolor
        /// </summary>
        /// <param name="color">[...]</param>
        private void SetSelectionColor(Color color)
        {
            selectionColor = color;
            selectionPen = new Pen(color);
            selectionPen.Width = 3;

            Invalidate();
        }

        /// <summary>
        /// Create the grid
        /// </summary>
        private void CreateGrid()
        {
            if ((TileWidth <= 0) && (TileHeight <= 0) || (gridPen == null))
                return;

            int width = TilesCountX * TileWidth;
            int height = TilesCountY * TileHeight;

            gridBmp = new Bitmap(width, height);
            CreateBitmaps(width, height);
            Graphics g = Graphics.FromImage(gridBmp);

            for (int xx = 1; xx < TilesCountX; xx++)
                g.DrawLine(gridPen, xx * TileWidth, 0, xx * TileWidth, height);
            for (int yy = 1; yy < TilesCountY; yy++)
                g.DrawLine(gridPen, 0, yy * TileHeight, width, yy * TileHeight);

            g.Dispose();
        }

        /// <summary>
        /// Set the size of a single tile
        /// </summary>
        /// <param name="both"></param>
        public void SetTileSize(int both)
        {
            SetTileSize(both, both);
        }

        /// <summary>
        /// Set the size of a single tile
        /// </summary>
        /// <param name="width"></param>
        /// <param name="height"></param>
        public void SetTileSize(int width, int height)
        {
            if ((width > 0) && (height > 0))
            {
                TileWidth = width;
                TileHeight = height;

                int fullHeight = Math.Max(1,TilesCountY * TileHeight);
                int fullWidth = Math.Max(1, TilesCountX * TileHeight);
                if (IsTilePicker)
                {
                    hScroll.Visible = false;
                    TilesCountX = Width / TileWidth;
                    if (tiles != null)
                        TilesCountY = (int)Math.Ceiling(tiles.Count / (float)TilesCountX);

                    fullHeight = TilesCountY * TileHeight;
                    if (fullHeight > Height)
                    {
                        vScroll.Visible = true;

                        TilesCountX = (Width - vScroll.Width) / TileWidth;
                        if (tiles != null)
                            TilesCountY = (int)Math.Ceiling(tiles.Count / (float)TilesCountX);

                        fullHeight = TilesCountY * TileHeight;

                        vScroll.Maximum = fullHeight - Height;
                    }
                    else
                    {
                        vScroll.Visible = false;
                    }
                    if (!vScroll.Visible)
                        vScroll.Value = 0;
                }
                else
                {
                    vScroll.Visible = true;
                    hScroll.Visible = true;

                    vScroll.Maximum = Math.Max(0, fullHeight - (Height - hScroll.Height));
                    hScroll.Maximum = Math.Max(0, fullWidth - (Width - vScroll.Width));
                }

                if (TilesCountX <= 0)
                {
                    TilesCountX = 1;
                    TileWidth = Width;
                }
                if (TilesCountY <= 0)
                {
                    TilesCountY = 1;
                    TileHeight = Height;
                }

                CreateBitmaps(Math.Max(1, fullWidth), Math.Max(1, fullHeight));

                CreateGrid();
                if (IsTilePicker)
                    GenerateTiles();
                Refresh();
            }
        }

        public void SetTileCount(int both)
        {
            SetTileCount(both, both);
        }

        public void SetTileCount(int width, int height)
        {
            if ((width > 0) && (height > 0))
            {
                TilesCountX = width;
                TilesCountY = height;

                width = (Width - vScroll.Width)/ TilesCountX;
                height = (Height - hScroll.Height)/ TilesCountY;

                SetTileSize(Math.Min(width, height));

                CreateGrid();
            }
        }

        private Point Tile2Point(Tile tile)
        {
            return new Point(tile.X, tile.Y);
        }

        private void DrawTile(Graphics g, Image image, int x, int y, ETileOrientation orientation)
        {
            Point[] points = null;
            
            Point a = new Point(x, y);
            Point b = new Point(x + TileWidth, y);
            Point c = new Point(x + TileWidth, y + TileHeight);
            Point d = new Point(x, y + TileHeight);

            switch (orientation)
            {
                case ETileOrientation.North:
                    points = new Point[3]{a, b, d};
                    break;
                case ETileOrientation.East:
                    points = new Point[3] { b, c, a };
                    break;
                case ETileOrientation.South:
                    points = new Point[3] { c, d, b };
                    break;
                case ETileOrientation.West:
                    points = new Point[3] { d, a, c };
                    break;
                default:
                    points = new Point[3] { a, b, d };
                    break;
            }

            g.DrawImage(image, points);
        }

        private void DrawTile(Graphics g, int type, int x, int y)
        {
            DrawTile(g, tileSet.GetImage(type), x * TileWidth, y * TileHeight, ETileOrientation.North);
        }

        private void DrawTile(Graphics g, int type, Tile tile)
        {
            DrawTile(g, tileSet.GetImage(type), tile.X * TileWidth, tile.Y * TileHeight, ETileOrientation.North);
        }

        private void DrawTile(Graphics g, Tile tile)
        {
            DrawTile(g, tileSet.GetImage(tile.Type), tile.X * TileWidth, tile.Y * TileHeight, tile.Orientation);
        }

        private void DrawSelection(Graphics g, Tile tile)
        {
            int xMin = Math.Min(TileSelectionStart.X, TileSelectionEnd.X);
            int xMax = Math.Max(TileSelectionStart.X, TileSelectionEnd.X);

            int yMin = Math.Min(TileSelectionStart.Y, TileSelectionEnd.Y);
            int yMax = Math.Max(TileSelectionStart.Y, TileSelectionEnd.Y);
            
            Point[] points = new Point[]
            {
                new Point((xMin + 0) * TileWidth + 1, (yMin + 0) * TileHeight + 1),
                new Point((xMax + 1) * TileWidth - 1, (yMin + 0) * TileHeight + 1),
                new Point((xMax + 1) * TileWidth - 1, (yMax + 1) * TileHeight - 1),
                new Point((xMin + 0) * TileWidth + 1, (yMax + 1) * TileHeight - 1),
                new Point((xMin + 0) * TileWidth + 1, (yMin + 0) * TileHeight + 1),
            };
            g.DrawLines(selectionPen, points);
        }

        protected override void OnResize(EventArgs eventargs)
        {
            base.OnResize(eventargs);
            SetTileSize(TileWidth);
            CreateGrid();
        }

        protected override void OnPaint(PaintEventArgs e)
        {
            Graphics g = Graphics.FromImage(bmp);
            g.Clear(BackColor);

            Graphics gS = Graphics.FromImage(bmpSelection);
            gS.Clear(Color.FromArgb(0, BackColor));

            if (tiles != null)
            {
                foreach (Tile tile in Tiles)
                    DrawTile(g, tile);

                if (SelectionMoveMode)
                    foreach (Tile tile in TilesSelected)
                        DrawTile(gS, tile);

                if (SelectionPasteMode)
                    foreach (Tile tile in TilesSelected)
                        DrawTile(gS, tile);
            }


            if ((ShowTileMouse) && (TileMouse != null) && mouseIn)
            {
                DrawTile(g, TileMouse);
            }

            if ((ShowTileSelected) && (TileSelected != null))
            {
                DrawSelection(g, TileSelected);
            }

            g.Dispose();

            e.Graphics.DrawImageUnscaled(bmp, -hScroll.Value, -vScroll.Value);

            if ((SelectionMoveMode) || (SelectionPasteMode))
            {
                Point p = new Point();
                p.X = SelectionMoveOffset.X * TileWidth - hScroll.Value;
                p.Y = SelectionMoveOffset.Y * TileHeight - vScroll.Value;

                if (SelectionMoveMode)
                {
                    e.Graphics.DrawImageUnscaled(bmpSelection, p);
                }

                if (SelectionPasteMode)
                {
                    e.Graphics.DrawImageUnscaled(bmpSelection, p);
                }
            }
            if (ShowGrid)
                e.Graphics.DrawImageUnscaled(gridBmp, -hScroll.Value, -vScroll.Value);
        }

        protected override void OnMouseMove(MouseEventArgs e)
        {
            base.OnMouseMove(e);
            TileMouse = MouseHitTest(e);

            if (ShowTileMouse)
            {
                Refresh();
            }
        }

        protected override void OnMouseLeave(EventArgs e)
        {
            base.OnMouseLeave(e);
            mouseIn = false;
            Refresh();
        }

        protected override void OnMouseEnter(EventArgs e)
        {
            base.OnMouseEnter(e);
            mouseIn = true;
            this.Focus();
        }

        private Point ScrollMouseEventArgs(MouseEventArgs e)
        {
            Point p = e.Location;
            if (hScroll.Visible)
                p.X += hScroll.Value;
            if (vScroll.Visible)
                p.Y += vScroll.Value;
            return p;
        }

        public Tile MouseHitTest(MouseEventArgs e)
        {
            Point p = ScrollMouseEventArgs(e);
            return new Tile(TileMouse.Type, TileMouse.Orientation, 
                Math.Min(TilesCountX - 1, p.X / TileWidth), 
                Math.Min(TilesCountY - 1, p.Y / TileHeight), 0);
        }

        public Tile GetFirstTileAt(MouseEventArgs e)
        {
            Point p = ScrollMouseEventArgs(e);
            int x = p.X / TileWidth;
            int y = p.Y / TileHeight;

            foreach(Tile tile in Tiles)
                if ((tile.X == x) && (tile.Y == y))
                    return tile;
            return null;
        }

        public Tile GetTileAt(MouseEventArgs e, int z)
        {
            Point p = ScrollMouseEventArgs(e);
            int x = p.X / TileWidth;
            int y = p.Y / TileHeight;

            foreach (Tile tile in Tiles)
                if ((tile.X == x) && (tile.Y == y) && (tile.Z == z))
                    return tile;
            return null;
        }

        public Tile[] GetTilesAt(MouseEventArgs e)
        {
            Point p = ScrollMouseEventArgs(e);
            Collection<Tile> t = new Collection<Tile>();

            int x = p.X / TileWidth;
            int y = p.Y / TileHeight;

            foreach (Tile tile in Tiles)
                if ((tile.X == x) && (tile.Y == y))
                    t.Add(tile);
            return t.ToArray<Tile>();
        }

        public void GenerateTiles()
        {
            if (tiles != null)
                tiles.Clear();
            else
                tiles = new Collection<Tile>();

            int count = 0;

            if (tileSet != null)
                foreach (TileInfo ti in tileSet.Tiles)
                {
                    tiles.Add(new Tile(ti.Type, ETileOrientation.North, count % TilesCountX, count / TilesCountX, 0));
                    count++;
                }
            CreateGrid();
        }

        public void DoSelectTile(Tile tile)
        {
            this.TileSelected = tile;
            DoSelectStart(tile);
            DoSelectEnd(tile);
        }

        public void DoSelectTile()
        {
            DoSelectTile(TileMouse);
        }

        public void DoSelectStart(Tile tile)
        {
            this.TileSelectionStart = tile;
        }

        public void DoSelectStart()
        {
            DoSelectStart(TileMouse);
        }

        public void DoSelectEnd(Tile tile)
        {
            this.TileSelectionEnd = tile;
        }

        public void DoSelectEnd()
        {
            DoSelectEnd(TileMouse);
        }

        public bool InsideSelection(Tile tile)
        {
            return ((tile.X >= Selection.X) && (tile.X <= Selection.Right)
                && (tile.Y >= Selection.Y) && (tile.Y <= Selection.Bottom));
        }

        private void CreateBitmaps(int width, int height)
        {
            bmp = new Bitmap(width, height);
            bmpSelection = new Bitmap(width, height);
        }

        public void SelectBoundingBox()
        {
            if (tiles != null)
            {
                if (tiles.Count > 0)
                {
                    int xMin = tiles[0].X;
                    int xMax = tiles[0].X;
                    int yMin = tiles[0].Y;
                    int yMax = tiles[0].Y;

                    foreach (Tile tile in tiles)
                    {
                        xMin = Math.Min(xMin, tile.X);
                        xMax = Math.Max(xMax, tile.X);
                        yMin = Math.Min(yMin, tile.Y);
                        yMax = Math.Max(yMax, tile.Y);
                    }

                    TileSelectionStart = new Tile(xMin, yMin, 0);
                    TileSelectionEnd = new Tile(xMax, yMax, 0);
                }
            }
        }
    }
}
